package iosu.java.games.razer.server;


import static iosu.java.games.razer.api.protocol.RazerProtocolBase.CARFINISHED_REQUEST;
import static iosu.java.games.razer.api.protocol.RazerProtocolBase.CARFINISHED_RESPONSE;
import static iosu.java.games.razer.api.protocol.RazerProtocolBase.CARUPDATE_REQUEST;
import static iosu.java.games.razer.api.protocol.RazerProtocolBase.CARUPDATE_RESPONSE;
import static iosu.java.games.razer.api.protocol.RazerProtocolBase.CLOSING_REQUEST;
import static iosu.java.games.razer.api.protocol.RazerProtocolBase.CREATEGAME_REQUEST;
import static iosu.java.games.razer.api.protocol.RazerProtocolBase.CREATEGAME_RESPONSE;
import static iosu.java.games.razer.api.protocol.RazerProtocolBase.GAMEFINISHED_REQUEST;
import static iosu.java.games.razer.api.protocol.RazerProtocolBase.GAMENOTREADY_RESPONSE;
import static iosu.java.games.razer.api.protocol.RazerProtocolBase.GAMEREADY_REQUEST;
import static iosu.java.games.razer.api.protocol.RazerProtocolBase.GAMEREADY_RESPONSE;
import static iosu.java.games.razer.api.protocol.RazerProtocolBase.HELLO_REQUEST;
import static iosu.java.games.razer.api.protocol.RazerProtocolBase.HELLO_RESPONSE;
import static iosu.java.games.razer.api.protocol.RazerProtocolBase.JOINGAME_REQUEST;
import static iosu.java.games.razer.api.protocol.RazerProtocolBase.NOGAMETOJOIN_RESPONSE;
import static iosu.java.games.razer.api.protocol.RazerProtocolBase.NOOP_RESPONSE;
import static iosu.java.games.razer.api.protocol.RazerProtocolBase.PLAYERADDED_RESPONSE;


import iosu.java.games.razer.ClientStatus;
import iosu.java.games.razer.api.CarInfo;
import iosu.java.games.razer.api.Client;
import iosu.java.games.razer.api.Game;
import iosu.java.games.razer.api.protocol.RazerProtocol;
import iosu.java.games.razer.api.protocol.RazerProtocolBinarySocket;
import iosu.java.games.razer.api.protocol.RazerProtocolMessage;

import java.awt.EventQueue;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Date;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.Vector;

import javax.swing.SwingUtilities;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class RazerServer  implements IRazerServer{

	private ServerSocketChannel ss = null;
	protected int Port;
	
	private int numclientes = 0;
	protected Vector<SocketChannel> clients = new Vector<SocketChannel>();
	// lista de clientes conectados al servidor
	protected Hashtable<String,Client> clientes = new Hashtable<String,Client>();
	// lista de partidas registradas
	protected Hashtable<Integer, Game> partidas = new Hashtable<Integer,Game>();
	// por velocidad de busqueda mantengo 4 listas
	// una con los juegos con un solo jugador
	protected Vector<Game> partidas1jugador = new Vector<Game>();
	// otra con los juegos con dos jugadores
	protected Vector<Game> partidas2jugadores = new Vector<Game>();
	// otra con juegos con 3 jugadores
	protected Vector<Game> partidas3jugadores = new Vector<Game>();
	// y la ultima con juegos con completos con 4 jugadores
	private Vector<Game> partidas4jugadores = new Vector<Game>();;
	private int gameseq;
	
	// para la admin del servidor
	private static final int DEFAULT_PORT=9911;
	private static final int ADMIN_PORT=8888;
	private static Logger logger;
	
	// GUI
	private static RazerServerGUI gui;
	
	static{
		logger= LoggerFactory.getLogger(RazerServer.class);
	}
	

	
	public RazerServer(int p) throws IOException {
		this.Port = p;
		this.ss = ServerSocketChannel.open();
		ss.configureBlocking(false);
		ss.socket().bind(new InetSocketAddress(this.Port));
		logger.debug("instanciado RazerServer");
	}

	public static void main(String[] args) throws IOException {
		int port = 0;
		// si no me llega el puerto como parametro
		if((args.length>0)&&(args[0]!=null)){
			port = Integer.parseInt(args[0]);
		}else{
			port = 9911;
		}
		// lanzo el servidor
		final RazerServer server = new RazerServer(port);
		// lanzo el GUI
        EventQueue.invokeLater(new Runnable() {
            public void run() {

             gui = new RazerServerGUI(server);
             gui.setVisible(true);

            }

       });
		// y arranco el servicio
		server.run();
	}
	
	private void invalidateGUI(){
		// invalido el gui para que lo repinte
		if(gui!=null){ 
			logger.info("invalidando gui");
			SwingUtilities.invokeLater(new Runnable(){
				public void run(){
					gui.revalidate();
					gui.repaint();
				}
			});
		}

	}

	public void run() {
		
		try {
			Selector selector = Selector.open();
			ss.register(selector, ss.validOps());
			logger.info("escuchando en {}", this.Port);
//			String readmessage = null;
			while (true) { // bucle de servicio del servidor
				logger.debug("server, inicio del loop eterno");
				// wait for events
				selector.select();
				// get keys
				Iterator<SelectionKey> i = selector.selectedKeys().iterator();
				// loop over keys
				
				while (i.hasNext()) {
					SelectionKey key = i.next();
//					SocketChannel client = null;
					// remove current key
					i.remove();
					if (!key.isValid()){
						// si la clave no es valida, paso a la siguiente
						continue;
					}else{
						// si la clave es valida, la proceso
						if(key.isAcceptable()){
							// acepto la conexion
							try{
//								client = 
								this.acceptConn(selector);
							}catch(IOException e){
								System.err.println(e.toString());
								continue;
							}
						}else if(key.isReadable()){
							// leo del socket
							ServerResponse sr = this.read(key);
							if(sr.getSt()==ServerState.NEW_CLIENT){
								// respondo un ACK al newclient
								// HELLOOK-<id.client>
								this.sendHelloOK(key);
								invalidateGUI();
							}else if(sr.getSt() ==ServerState.CREATEGAMEOK){
								// envio creategameok y el num de coche
								this.sendCreateGameOk(key,sr.getI());
								invalidateGUI();
							}else if(sr.getSt()==ServerState.PLAYERADDED){
								// envio PLAYERADDED y el num del coche
								this.sendPlayerAdded(key, sr.getI());
								invalidateGUI();
							}else if(sr.getSt()==ServerState.GAMEREADY){
								this.sendGameReady(key, sr.getNames());
								invalidateGUI();
							}else if(sr.getSt()==ServerState.GAMENOTYETREADY){
								// contesto cualquier cosa que no sea GAMEREADY
								// para que los clientes no cambien de estado y sigan preguntando
								this.sendLaunchingGame(key, sr.getI());
								
							}else if(sr.getSt()==ServerState.CARUPDATED){
								// he recibido actualizacion de un coche
								// contesto con los 4 coches al que me ha mandado el update
								this.sendGeneralUpdate(key, sr.getI());
							}else if(sr.getSt()==ServerState.NOGAMETOJOIN){
								this.sendNoGameToJoin(key);
							}else if(sr.getSt()==ServerState.CLIENT_CLOSING){
								// no contesto nada, pero elimino el cliente del vector de clientes
								clientes.remove(Integer.toString(sr.getI()));
								clients.remove((SocketChannel)key.channel());
								invalidateGUI();
								// no lo elimino de las partidas hasta que terminen para que no entre otro jugador
							}else if(sr.getSt()==ServerState.GAMEFINISHED){
								// ha terminado la partida
								Client tmpc = clientes.get(String.valueOf(sr.getI()));
								Game g = tmpc.getGame();
//								System.out.println("recibido partida terminada? de "+tmpc.getClientid());
								if(g.isGameFinished()){
									// si ya han terminado todos, contesto con el resultado
									logger.debug("partida terminada");
//									System.out.println("serveR: partida terminada "+g.getGameid()+" para "+tmpc.getClientid());
									this.sendGameFinished(key, g);
								}else{
								// si no han terminado todos aun, contesto al cliente que siga preguntando
									logger.debug("no han terminado todos");
//									System.out.println("serveR: no han terminado todos");
									this.sendNoop(key);
								}
								invalidateGUI();
							}else if(sr.getSt()==ServerState.CARFINISHED){
								// un coche ha terminado
								
								// miro a ver si era el ultimo y la partida ha acabado
								Client tmpc = clientes.get(String.valueOf(sr.getI()));
								Game g = tmpc.getGame();
//								System.out.println("el coche "+sr.getI()+" ha terminado");
//								System.out.println("partida terminada? "+g.isGameFinished());
								if(g.isGameFinished()){
									// si ya han terminado todos, contesto con el resultado
									// el cliente que me ha preguntado ha llegado el ultimo
									logger.debug("partida terminada");
//									System.out.println("serveR: contesto partida terminada "+g.getGameid()+" al ultimo");
									this.sendGameFinished(key, g);
								}/*else if(sr.getSt()==ServerState.NOOP){
									this.sendNoop(key);
								}*/else{
								// si no han terminado todos aun, contesto al cliente que siga preguntando
									this.sendNoop(key);
									logger.debug("no han terminado todos, contesto un noop a "+tmpc.getClientid());
//									System.out.println("serveR: no han terminado todos");
								}
							}else{
								// por defecto si no tengo un estado definido contesto un NOOP
								// para que el cliente no se bloquee
//								System.out.println("contestando NOOP generico");
								logger.debug("enviando NOOP, estado = "+sr.getSt());
								this.sendNoop(key);
							}
						}else if(key.isWritable()){
							// escribo al socket
//							this.write(key);
						}
					}
				} // while
				
			} // while (true)
		} catch (Exception e) {
			logger.error("Error en el bucle de escucha",e);
		}
	}
	
	private SocketChannel acceptConn(Selector selector) throws IOException{
		// get client socket channel
		SocketChannel client = ss.accept();
		if(client!=null){
			client.configureBlocking(false);
			client.register(selector, SelectionKey.OP_READ);
			clients.add(client);
			logger.info("Client connection from {}",  client.getRemoteAddress( ));
		}
		return client;

	}

	private ServerResponse read(SelectionKey key) throws IOException{
		SocketChannel socketChannel = (SocketChannel) key.channel();
		ByteBuffer readBuffer = ByteBuffer.allocate(512);
		
	    // Clear out our read buffer so it's ready for new data
	    readBuffer.clear();
	    // Attempt to read off the channel
	    int numRead=0;
	    try {
	      numRead = socketChannel.read(readBuffer);
	      logger.debug("read: esperando {} bytes en el buffer",numRead);
	    } catch (IOException e) {
	      // The remote forcibly closed the connection, cancel
	      // the selection key and close the channel.
	      key.cancel();
	      socketChannel.close();
	      return new ServerResponse(ServerState.COULD_NOT_READ_FROM_SOCKET);
	    }

	    if (numRead == -1) {
	      // Remote entity shut the socket down cleanly. Do the
	      // same from our end and cancel the channel.
	      key.channel().close();
	      key.cancel();
	      return new ServerResponse(ServerState.PEER_CLOSED_SOCKET);
	    }
	    readBuffer.flip();
	    byte[] effectiveRead = new byte[numRead];
	    readBuffer.get(effectiveRead,0,numRead);
	    return processReceivedMessage(ByteBuffer.wrap(effectiveRead));
	}
	
	protected ServerResponse processReceivedMessage(ByteBuffer b){
		RazerProtocolMessage msg = RazerProtocolBinarySocket.decodeMessage(b.array());
		logger.debug("processReceivedMessage: recibido {}", b.array());
		if(msg== null) throw new RuntimeException("processReceivedMessage: msg invalido " + msg +" generado a partir de "+b.toString());
		switch(msg.getMessagetype()){
		case HELLO_REQUEST:
			// si recibo una peticion HELLO_REQUEST
			logger.debug("processReceivedMesssage: peticion de nuevo cliente");
			return registrarNuevoCliente(msg);
		case JOINGAME_REQUEST:
			logger.debug("processReceivedMessage: peticion de unirse a un juego");
			// si no hay juegos, ignoro el join
			if(partidas.size()<1){
				logger.debug("process:receivedMessage: no hay ninguna partida en curso");
				return new ServerResponse(ServerState.NOGAMETOJOIN);
			}
			// busco partidas con 3
			ServerResponse sr=null;
			if(partidas3jugadores.size()>0){
				logger.debug("buscando en partidas de 3 jugadores...");
				sr=buscarEnPartidasDe3Jugadores(msg);
				logger.debug("processReceivedMessage: buscarEnPartidasDe3Jugadores "+sr);
			// si no hay, busco en las de 2 jugadores
			}else if(partidas2jugadores.size()>0){
				logger.debug("processReceivedMessage: no hay partidas de 3 jugadores, buscando en partidas de 2...");
				sr=buscarEnPartidasDe2Jugadores(msg);
				// si he llegado aqui no puede ser que no haya partidas de 1 al menos
			}else{
				logger.debug("processReceivedMessage: no hay partidas de 2 jugadores, buscando en partidas de 1...");
				sr=buscarEnPartidasDe1Jugador(msg);
			}
			logger.debug("processReceivedMessage: JOINGAME_REQUEST devolviendo "+sr.getSt());
			return sr;
		case CREATEGAME_REQUEST:
			logger.debug("processReceivedMessage: peticion de crear una partida");
			if(msg.getIdfromserver()!=0){
				return crearNuevaPartida(msg);
			}else{
				logger.warn("processReceivedMessage: recibido CREATEGAME sin id de cliente, lo ignoro");
				return new ServerResponse(ServerState.NOOP);
			}
		case GAMEREADY_REQUEST:
			logger.debug("processReceivedMessage: consulta sobre estado de una partida");
			// TODO: fake!!
			/* return new ServerResponse(ServerState.GAMEREADY, 
					new String[]{"uno","dos","tres","cuatro"});*/
			// busco en la lista de clientes por el id
			
			  if(msg.getIdfromserver()!=0){
			 
				Client tmpc = clientes.get(String.valueOf(msg.getIdfromserver()));
				// recupero la partida asociada
				Game g  = tmpc.getGame();
				// si hay 4 jugadores, contesto GAMEREADY
				// si no los hay, contesto GAMENOTYETREADY
				if(g!=null){
					if(g.getNumberOfPlayers()<4){
						logger.debug("processReceivedMessage: hay menos de 4 jugadores, la partida no esta lista");
						return new ServerResponse(ServerState.GAMENOTYETREADY,g.getNumberOfPlayers());
					}else{
						logger.debug("processReceivedMessage: hay 4 jugadores, la partida esta lista");
						g.setGameStatus(GameStatus.READY);
						logger.debug("processReceivedMessage: "+g.getCar1());
						logger.debug("processReceivedMessage: "+g.getCar2());
						logger.debug("processReceivedMessage: "+g.getCar3());
						logger.debug("processReceivedMessage: "+g.getCar4());
						String[] players = {g.getCar1().getName(), g.getCar2().getName(), 
								g.getCar3().getName(), g.getCar4().getName()};
					// 	el ultimo jugador siempre llevara el coche 4
						logger.debug("processReceivedMessage: devolviendo GAMEREADY");
						return new ServerResponse(ServerState.GAMEREADY, players);
					}
				}else{
					logger.warn("processReceivedMessage: el idcliente {} no tiene partida asociada... o_O",msg.getIdfromserver());
				}
			}
			break; 
		case CARUPDATE_REQUEST:
			// CARSTATUS-car,x,y,orientation,turningleft, turningright, accelerating, braking
			logger.debug("processReceiveMessage: peticion de actualizacion de estado del jugador {}",msg.getIdfromserver());
			// recibo un status del coche idcliente
			if(msg.getIdfromserver()!=0){
				Client tmpc = clientes.get(String.valueOf(msg.getIdfromserver()));
				if(tmpc==null){
					logger.warn("el jugador {} no esta registado como cliente", msg.getIdfromserver());
					// contesto con un noop, el cliente se quedara esperando
					return new ServerResponse(ServerState.NOOP);
				}
				Game g = tmpc.getGame();
				if(g==null){
					logger.warn("el jugador {} no esta asociado a ninguna partida",msg.getIdfromserver());
					// contesto con un noop, el cliente se quedara esperando
					return new ServerResponse(ServerState.NOOP);
				}
				// y ahora actualizo el coche en el juego
//				tmpc.setCar(msg.getDefaultcarupdate());
				switch(msg.getNumCar()){
				case 1:
					g.updateCar1(tmpc);
					break;
				case 2:
					g.updateCar2(tmpc);
					break;
				case 3:
					g.updateCar3(tmpc);
					break;
				case 4:
					g.updateCar4(tmpc);
					break;
				}
				logger.debug("actualizando coche {} en partida {}",msg.getNumCar(),g.getGameid());
				return new ServerResponse(ServerState.CARUPDATED, g.getGameid().intValue());
			}else{
				// el idfromserver es 0.. ya me explicaras tu como ha pasado... 
			}
			break;
		case CLOSING_REQUEST:
			// el cliente se desconecta
			return new ServerResponse(ServerState.CLIENT_CLOSING, msg.getIdfromserver());
		case GAMEFINISHED_REQUEST:
			// el cliente pregunta si su partida ha terminado
			// busco el cliente
//			System.out.println("recibida partida terminada? de cliente "+msg.getIdfromserver());
			if(msg.getIdfromserver()!=0){
				Client tmpc = clientes.get(String.valueOf(msg.getIdfromserver()));
				Game g = tmpc.getGame();
				if(g==null){
					logger.warn("processReceivedMessage: GAMEFINISHED_REQUEST: el jugador {} no esta asociado a ninguna partida",msg.getIdfromserver());
					// contesto con un noop, el cliente se quedara esperando
					return new ServerResponse(ServerState.NOOP);
				}
				if(g.isGameFinished()){
					// contesto con que si que ha terminado
//					System.out.println("la carrera en la partida "+g.getGameid()+" ha terminado");
					return new ServerResponse(ServerState.GAMEFINISHED, g.getGameid());
				}else{
					// contesto con noop
//					System.out.println("la carrera en la partida "+g.getGameid()+" NO ha terminado");
					return new ServerResponse(ServerState.NOOP);
				}
			}
			break;
		case CARFINISHED_REQUEST:
			// un cliente me dice que ha terminado la carrera
//			System.out.println("recibido carfinished request de "+msg.getIdfromserver());
			if(msg.getIdfromserver()!=0){
				Client tmpc = clientes.get(String.valueOf(msg.getIdfromserver()));
				Game g = tmpc.getGame();
				if(g==null){
					logger.warn("processReceivedMessage: CARFINISHED_REQUEST: el jugador {} no esta asociado a ninguna partida",msg.getIdfromserver());
					// contesto con un noop, el cliente se quedara esperando
					return new ServerResponse(ServerState.NOOP);
				}
				// tengo la partida en g
				g.finishCar(msg.getIdfromserver());
//				System.out.println("finalizo el "+msg.getIdfromserver()+" en la partida "+g.getGameid());
				logger.debug("processReceivedMessage: CARFINISHED_REQUEST: el jugador "+msg.getIdfromserver()+" ha terminado la carrera");
				return new ServerResponse(ServerState.CARFINISHED, msg.getIdfromserver());
			}
			break;
		default:
			// para cualquier mensaje que no encaje en los anteriores, respondo un NOOP y no hago nada
			logger.warn("processReceivedMessage: respondiendo NOOP a mensaje recibido {}",msg);
			return new ServerResponse(ServerState.NOOP);
				
		}
		return null;
	}
	
	protected ServerResponse crearNuevaPartida(RazerProtocolMessage msg){
		Game g = new Game();
		g.setCreationtime(new Date(System.currentTimeMillis()));
		g.setGameStatus(GameStatus.LAUNCHING);
		logger.debug("crearNuevaPartida: cliente {}",msg.getIdfromserver());
		Client tmpc = clientes.get(String.valueOf(msg.getIdfromserver()));
		try{
			g.setCar1(tmpc);
			g.setOwnerid(tmpc.getId());
			g.setGameid(generateGameId());
			tmpc.setCar(CarInfo.defaultCar1());
			tmpc.setGame(g);
			partidas.put(g.getGameid(), g);
			logger.debug("crearNuevaPartida: creada nueva partida {}, insertada en partidas1jugador",g.getGameid());
			partidas1jugador.add(g);
			return new ServerResponse(ServerState.CREATEGAMEOK, 1);
		}catch(AlreadyBoundCar a){
			logger.error("crearNuevaPartida: error creando partida {} para jugador {}",g.getGameid(),tmpc.getId(),a);
			return new ServerResponse(ServerState.GAME_SETTINGS_ERROR);
		}
	}
	
	protected ServerResponse buscarEnPartidasDe3Jugadores(RazerProtocolMessage msg){
		logger.debug("buscarEnPArtidasDe3Jugadores: buscando entre partidas de 3 jugadores");
		Game g = partidas3jugadores.firstElement();
		// solo faltaria el car4
		if(msg.getIdfromserver()!=0){
			Client tmpc = clientes.get(String.valueOf(msg.getIdfromserver()));
			try{
				//lo elimino de partidas3jugadores
				partidas3jugadores.remove(g);
				g.setCar4(tmpc);
				g.setGameStatus(GameStatus.WAITING_FOR_PLAYERS);
				tmpc.setCar(CarInfo.defaultCar4());
				tmpc.setGame(g);
				logger.debug("buscarEnPartidasDe3jugadores: eliminando {} de partidas de 3 jugadores, insertando en 4 jugadores con nuevo jugador {}",g.getGameid(),tmpc.getClientid());
				// y lo inserto en 4 jugadores
				partidas4jugadores.add(g);
				g.setGameStatus(GameStatus.READY);
				String[] players = {g.getCar1().getName(), g.getCar2().getName(), 
						g.getCar3().getName(), g.getCar4().getName()};
				return new ServerResponse(ServerState.GAMEREADY, players);
			}catch(AlreadyBoundCar a){
				logger.error("buscarEnPartidasDe3Jugadores: error uniendo el jugador {} a la partida {}",tmpc.getId(),g.getGameid(),a);
				return new ServerResponse(ServerState.GAME_SETTINGS_ERROR);
			}
		}else{
			logger.warn("buscarEnPartidasDe3Jugadores: recibido JOINGAME sin id de cliente, lo ignoro");
			return new ServerResponse(ServerState.NOOP);
		}
	}
	
	protected ServerResponse buscarEnPartidasDe2Jugadores(RazerProtocolMessage msg){
		logger.debug("buscarEnPartidasDe2Jugadores: buscando entre partidas de 2 jugadores");
		Game g = partidas2jugadores.firstElement();
		// car3
		if(msg.getIdfromserver()!=0){
			Client tmpc = clientes.get(String.valueOf(msg.getIdfromserver()));
			try{
				//lo elimino de partidas2jugadores
				partidas2jugadores.remove(g);
				g.setCar3(tmpc);
				tmpc.setCar(CarInfo.defaultCar3());
				tmpc.setGame(g);
				logger.debug("buscarEnPartidasDe2jugadores: eliminando {} de partidas de 2 jugadores, insertando en 3 jugadores con nuevo jugador {}",g.getGameid(),tmpc.getClientid());
				// y lo inserto en 3 jugadores
				partidas3jugadores.add(g);
				return new ServerResponse(ServerState.PLAYERADDED, 3);
			}catch(AlreadyBoundCar a){
				logger.error("buscarEnPartidasDe2Jugadores: error uniendo el jugador {} a la partida {}",tmpc.getId(),g.getGameid(),a);
				return new ServerResponse(ServerState.GAME_SETTINGS_ERROR);
			}
		}else{
			logger.warn("buscarEnPartidasDe2Jugadores: recibido JOINGAME sin id de cliente, lo ignoro");
			return new ServerResponse(ServerState.NOOP);
		}
	}
	
	protected ServerResponse buscarEnPartidasDe1Jugador(RazerProtocolMessage msg){
		logger.debug("buscarEnPArtidasDe1Jugador: buscando entre partidas de 1 jugador");
		Game g = partidas1jugador.firstElement();
		// car2
		if(msg.getIdfromserver()!=0){
			Client tmpc = clientes.get(String.valueOf(msg.getIdfromserver()));
			try{
				//lo elimino de partidas1jugador
				partidas1jugador.remove(g);
				g.setCar2(tmpc);
				tmpc.setCar(CarInfo.defaultCar2());
				tmpc.setGame(g);
				logger.debug("buscarEnPartidasDe1jugador: eliminando {} de partidas de 1 jugador, insertando en 2 jugadores con nuevo jugador {}",g.getGameid(),tmpc.getClientid());
				// y lo inserto en 2 jugadores
				partidas2jugadores.add(g);
				return new ServerResponse(ServerState.PLAYERADDED, 2);
			}catch(AlreadyBoundCar a){
				logger.error("buscarEnPartidasDe2Jugadores: error uniendo el jugador {} a la partida {}",tmpc.getId(),g.getGameid(),a);
				return new ServerResponse(ServerState.GAME_SETTINGS_ERROR);
			}
		}else{
			logger.warn("buscarEnPartidasDe2Jugadores: recibido JOINGAME sin id de cliente, lo ignoro");
			return new ServerResponse(ServerState.NOOP);
		}
	}
	
		
	private Integer generateGameId(){
		this.gameseq++;
		logger.debug("generando id de partida ",this.gameseq);
		return new Integer(this.gameseq);
		
	}

	
	private void sendHelloOK(SelectionKey key){
		logger.debug("sendHelloOK: enviando HELLO_RESPONSE");
		RazerProtocolBinarySocket.send(RazerProtocolBinarySocket.createHelloResponse(numclientes),key);
	}
	

	private void sendNoGameToJoin(SelectionKey key){
		logger.debug("sendNoGameToJoin: enviando NOGAMETOJOIN_RESPONSE");
		RazerProtocolBinarySocket.send(RazerProtocolBinarySocket.createNoGameToJoinResponse(),key);
	}
	
	private void sendNoop(SelectionKey key){
		logger.debug("sendNoop: enviando NOOP");
		RazerProtocolBinarySocket.send(RazerProtocolBinarySocket.createNoopResponse(), key);
	}
	
	private void sendLaunchingGame(SelectionKey key, int numjugadores){
		logger.debug("sendLaunchingGame: enviando GAMENOTREADY_RESPOSE");
		RazerProtocolBinarySocket.send(RazerProtocolBinarySocket.createGameNotReadyResponse(numjugadores),key);
	}
	
	private void sendCreateGameOk(SelectionKey key, int numcar) throws IOException{
		logger.debug("sendCreateGameOk: enviando CREATEGAME_RESPONSE");
		RazerProtocolBinarySocket.send(RazerProtocolBinarySocket.createCreateGameResponse(numcar), key);
	}
	
	private void sendPlayerAdded(SelectionKey key, int numcar){
		logger.debug("sendPlayerAdded: enviando PLAYERADDED_RESPONSE");
		RazerProtocolBinarySocket.send(RazerProtocolBinarySocket.createPlayerAddedResponse(numcar), key);
	}

	private void sendGameReady(SelectionKey key, String[] names){
		logger.debug("sendGameReady: enviando GAMEREADY_RESPONSE");
		RazerProtocolBinarySocket.send(RazerProtocolBinarySocket.createGameReadyResponse(names), key);
	}

	

	private void sendGeneralUpdate(SelectionKey key, int idgame){
		logger.debug("sendGameReady: enviando CARUPDATE_RESPONSE");
		Game game = partidas.get(Integer.valueOf(idgame));
		if(game!=null){
			RazerProtocolBinarySocket.send(RazerProtocolBinarySocket.createCarUpdateResponse(game), key);
		}else{
			logger.error("Hay {} partidas, pero no se ha encontrado partida con id {}", partidas.size(),idgame);
		}
	}
	
	private void sendGameFinished(SelectionKey key, Game g){
		logger.debug("sendGameFinished: enviando CARFINISHED_RESPONSE");
		RazerProtocolBinarySocket.send(RazerProtocolBinarySocket.createCarFinishedResponse(g), key);
	}
	
	
	protected ServerResponse registrarNuevoCliente(RazerProtocolMessage msg){
		Client c = new Client();
		c.setStart_time(new Date(msg.getHora()));
		c.setId(msg.getIdcliente().toString());
		// esto tengo que revisarlo y usar una secuencia como dios manda...
		c.setClientid(++numclientes);
		// este estado creo que no vale para nada.. lo quito?
		c.setStatus(ClientStatus.WAITING_FOR_HELLOOK);
		c.setName(msg.getPlayername());
		logger.info("anyadiendo cliente {} con id='{}' {}", c.getName(), c.getClientid(), c.toString());
		clientes.put(Integer.toString(c.getClientid()),c);
		return new ServerResponse(ServerState.NEW_CLIENT, Integer.toString(c.getClientid()));
	}
	
	
	public void shutdown(){
		logger.info("shutdown: parando servidor...");
		try{
			this.ss.close();
		}catch(IOException e){
			logger.error("error parando servidor",e);
			logger.info("shutdown: System.exit -1");
			System.exit(-1);
		}
	}

}